package org.pettyfox.utils.io.bis;

import cn.hutool.core.bean.BeanDesc;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.watch.SimpleWatcher;
import cn.hutool.core.io.watch.WatchUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import org.pettyfox.utils.io.bis.annotation.IniField;
import org.pettyfox.utils.io.bis.annotation.IniFile;
import org.pettyfox.utils.io.bis.bean.BaseBeanIni;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.util.*;

/**
 *     
 * ini文件读写类，ini即配置文件
 *
 * @author pettyfox.top  
 * @version V1.0   
 * @date 2020-05-09
 */
public class IniFileWriteRead<T extends BaseBeanIni> {

    /**
     * 注解前缀，不建议使用分号，分号容易造成中英文问题
     */
    private static final char[] COMMENT_PREFIX = {'#', ';', '-', '；'};

    /**
     * 配置文件，键值对分隔符
     */
    private static final char KEY_VALUE_SPITE = '=';
    private static final char FORMAT = ' ';
    private static final String INI_VERSION = "version";
    /**
     * 键值对缓存，按分组的方式
     */
    private final LinkedHashMap<String, LinkedHashMap<String, String>> kvCache = new LinkedHashMap<>();

    /**
     * 键值对注解缓存
     */
    private final LinkedHashMap<String, LinkedHashMap<String, List<String>>> kvCommentsCache = new LinkedHashMap<>();
    private Charset charset;
    private String iniFileStorePath;
    private T bean;
    private int iniVersion = 0;
    private boolean autoRefreshFile = true;


    public IniFileWriteRead(T bean) throws IOException {
        this.bean = bean;
        int version = bean.getVersion();
        bean2cache();
        file2cache();
        addCache("", INI_VERSION, String.valueOf(bean.getVersion()));
        cache2Bean();
        if (iniVersion < version) {
            bean2file();
        }
        if(autoRefreshFile){
            monitorFile();
        }
    }

    public void bean2cache() throws IOException {
        kvCommentsCache.clear();
        IniFile iniFile = bean.getClass().getAnnotation(IniFile.class);
        this.autoRefreshFile = iniFile.autoRefreshFile();
        this.charset = Charset.forName(iniFile.charset());
        this.iniFileStorePath = FileUtil.touch(iniFile.storePath().replace("\\", File.separator).replace("/", File.separator)).getCanonicalPath();
        this.addComment("", "", Arrays.asList(iniFile.fileComments()));
        Collection<BeanDesc.PropDesc> PropDescs = BeanUtil.getBeanDesc(bean.getClass()).getProps();
        for (BeanDesc.PropDesc field : PropDescs) {
            IniField iniField = field.getField().getAnnotation(IniField.class);
            if (null != iniField) {
                try {
                    addCache(iniField.group(), field.getFieldName(), String.valueOf(Optional.ofNullable(field.getGetter().invoke(bean)).orElse("")));
                    addComment(iniField.group(), field.getFieldName(), Arrays.asList(iniField.comments()));
                    addComment(iniField.group(), iniField.group(), Arrays.asList(iniField.groupComments()));

                } catch (InvocationTargetException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void cache2Bean() {
        kvCache.forEach((group, map) -> {
            map.forEach((key, value) -> {
                writeBean(group, key, value);
            });
        });
    }

    private void writeBean(String group, String key, String value) {
        Collection<BeanDesc.PropDesc> PropDescs = BeanUtil.getBeanDesc(bean.getClass()).getProps();
        for (BeanDesc.PropDesc field : PropDescs) {
            IniField iniField = field.getField().getAnnotation(IniField.class);
            if (null != iniField) {
                try {
                    if (iniField.group().equals(group) && field.getFieldName().equals(key)) {
                        if (field.getFieldClass() == Integer.class || field.getFieldClass() == int.class) {
                            field.getSetter().invoke(bean, Integer.valueOf(value));
                        } else if (field.getFieldClass() == Long.class || field.getFieldClass() == long.class) {
                            field.getSetter().invoke(bean, Long.valueOf(value));
                        } else if (field.getFieldClass() == Boolean.class || field.getFieldClass() == boolean.class) {
                            field.getSetter().invoke(bean, value.toUpperCase().equals("TRUE") || "1".equals(value));
                        } else if (field.getFieldClass() == String.class) {
                            field.getSetter().invoke(bean, value);
                        }
                    }

                } catch (InvocationTargetException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void addCache(String group, String key, String value) {
        kvCache.computeIfAbsent(group, k -> new LinkedHashMap<>(128))
                .put(key, value);
    }

    private void addComment(String group, String key, List<String> values) {
        kvCommentsCache.computeIfAbsent(group, k -> new LinkedHashMap<>())
                .computeIfAbsent(key, k -> new ArrayList<>())
                .addAll(values);
    }

    public synchronized void file2cache() throws IOException {
        BufferedReader reader = null;
        try {
            reader = IoUtil.getReader(new FileInputStream(iniFileStorePath), this.charset);
            String group = "";
            String line;
            while (true) {
                line = reader.readLine();
                if (line == null) {
                    break;
                }
                line = line.trim();

                if (StrUtil.isBlank(line)) {
                    continue;
                }

                boolean isComment = false;
                for (char p : COMMENT_PREFIX) {
                    isComment = line.startsWith(String.valueOf(p));
                    if (isComment) {
                        break;
                    }
                }
                if (isComment) {
                    continue;
                }
                String key;
                String value;
                if (StrUtil.isSurround(line, CharUtil.BRACKET_START, CharUtil.BRACKET_END)) {
                    group = line.substring(1, line.length() - 1).trim();
                    key = "";
                    value = "";
                } else {
                    final String[] keyValue = StrUtil.splitToArray(line, KEY_VALUE_SPITE, 2);

                    if (keyValue.length < 2) {
                        continue;
                    }
                    key = keyValue[0].trim();
                    value = keyValue[1].trim();

                    if("".equals(group) && INI_VERSION.equals(key)){
                        iniVersion = Integer.parseInt(value);
                    }
                }
                addCache(group, key, value);
            }
        } finally {
            IoUtil.close(reader);
        }
    }

    public void bean2file() throws IOException {
        bean2file(iniFileStorePath);
    }

    public synchronized void bean2file(String path) throws IOException {
        PrintWriter writer = null;
        try {
            writer = FileUtil.getPrintWriter(path, charset, false);
            PrintWriter finalWriter = writer;
            kvCache.forEach((group, map) -> {
                LinkedHashMap<String, List<String>> commentMap = kvCommentsCache.get(group);
                writeln(commentMap.get(group), finalWriter);
                finalWriter.println(CharUtil.BRACKET_START + group + CharUtil.BRACKET_END);
                map.forEach((key, value) -> {
                    if ("".equals(key)) {
                        return;
                    }
                    writeln(commentMap.get(key), finalWriter);
                    finalWriter.println(key + FORMAT + KEY_VALUE_SPITE + FORMAT + value);
                    finalWriter.println();
                });
            });
        } finally {
            IoUtil.close(writer);
        }
    }

    private void writeln(List<String> list, PrintWriter writer) {
        if (null == list) {
            return;
        }
        list.forEach(c -> writer.println(String.valueOf(COMMENT_PREFIX[0]) + FORMAT + c));
    }

    public void monitorFile() {
        WatchUtil.createModify(this.iniFileStorePath, new SimpleWatcher() {
            @Override
            public void onModify(WatchEvent<?> event, Path currentPath) {
                try {
                    file2cache();
                    cache2Bean();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
